package de.sordul.md5crack.impl.optimizedMultiCore;

import java.util.ArrayList;
import java.util.List;

import de.sordul.md5crack.IFragmentBuilder;
import de.sordul.md5crack.util.Charset;

public class FragmentBuilder implements IFragmentBuilder {

	private static List<Character> unicode = null;
	private List<Character> returnCharacters = new ArrayList<Character>();
	private String returnString = null;
	private int MAX_CHARACTER = 0, charNumber, intervall;
	private StringBuilder builder;
	
	/**
	 * Initialize the unicode and returnCharacters lists.
	 */
	public FragmentBuilder(Charset charset, int intervall, int startChar) {
		builder = new StringBuilder();
		this.intervall = intervall-1;
		
		// Check for charset. LATIN is default
		switch(charset) {
			case LATIN:
				MAX_CHARACTER = 126;
				break;
			case LATIN_EXT:
				MAX_CHARACTER = 591;
				break;
			case FULL:
				MAX_CHARACTER = 65535;
				break;
			default:
				MAX_CHARACTER = 126;
				break;
		}
		
		if(unicode == null) {
			unicode = buildUnicodeList(); 
		}
		
		returnCharacters.add(unicode.get(startChar));
	}
	
	public synchronized String getNextString() {
		returnString = getStringRepresentation(returnCharacters);
		increaseString(returnCharacters);

		return returnString;	
	}
	
	/**
	 * Increase the string represented by the character list
	 * @param returnCharacters List of Characters
	 */
	private void increaseString(List<Character> charList) {

		// Ignore empty lists
		if(charList.isEmpty()) {
			return;
		}
		
		// The last char in list is smaller than MAX_CHARACTER
		int charNumber = (int)charList.get(charList.size()-1);
		charNumber += intervall;
		if( charNumber < MAX_CHARACTER ) {
			charList.set(charList.size()-1, (char)++charNumber);
		}
		
		// The last char is @ max. Try to increase predecessors, append
		// a new one if all are @ max.
		else {
			if(increasePredecessor(charList) == false) {
				resetPredecessors(charList);
				charList.add(unicode.get(0));
			}
			else {
				charList.set(charList.size()-1, unicode.get(0));
			}
		}
	}

	/**
	 * Set all predecessors of the last char to the first unicode char.
	 * @param charList List of characters
	 */
	private void resetPredecessors(List<Character> charList) {
		char firstChar = unicode.get(0);
		for(int i=charList.size()-1; i >= 0; i--) {
			charList.set(i, firstChar);
		}
	}

	/**
	 * Try to increase the next predecessor in list. If impossible return FALSE, TRUE otherwise.
	 * Last char in list is ommited.
	 * 
	 * @param charList List of Characters
	 * @return TRUE if it was possible to increase a predecessor, FALSE otherwise
	 */
	private boolean increasePredecessor(List<Character> charList) {

		for(int i=charList.size()-2; i >= 0 ; i--) {
			
			// Check if this predecessor is @ max. If not increase it and return TRUE
			charNumber = (int)charList.get(i);
			if( charNumber < MAX_CHARACTER ) {
				charList.set(i, (char)++charNumber);
				
				for(int j=i+1; j < charList.size()-1; j++) {
					charList.set(j, unicode.get(0));
				}
				
				return true;
			}
		}
		
		return false;
	}

	/**
	 * Creates a List containing all unicode characters. Only alphanumerics are returned.
	 * @return List of all unicode characters
	 */
	private List<Character> buildUnicodeList() {
		List<Character> unicode = new ArrayList<Character>();
		char current;
		
		// Iterate through all possible unicode values
		for(int i=0; i <= MAX_CHARACTER; i++) {
			current = (char)i;
			if(Character.isAlphabetic(current) || Character.isDigit(current)) {
				unicode.add(current);
			}
		}
		
		return unicode;
	}

	/**
	 * Converts the char List to an array and returns it.
	 * >> Adapted from: http://stackoverflow.com/questions/6324826
	 * 
	 * @param list List of characters
	 * @return A String
	 */
	private String getStringRepresentation(List<Character> list) {    
		builder.delete(0, builder.length());
		
	    for(Character ch: list) {
	        builder.append(ch);
	    }
	    return builder.toString();
	}


}
